/*******************************************************************************
 * Copyright 2009, 2010 Innovation Gate GmbH. All Rights Reserved.
 * 
 * This file is part of the OpenWGA server platform.
 * 
 * OpenWGA is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * In addition, a special exception is granted by the copyright holders
 * of OpenWGA called "OpenWGA plugin exception". You should have received
 * a copy of this exception along with OpenWGA in file COPYING.
 * If not, see <http://www.openwga.com/gpl-plugin-exception>.
 * 
 * OpenWGA is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with OpenWGA in file COPYING.
 * If not, see <http://www.gnu.org/licenses/>.
 ******************************************************************************/
package de.innovationgate.webgate.api.templates;

import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

import org.apache.log4j.Logger;

import de.innovationgate.webgate.api.WGACLCore;
import de.innovationgate.webgate.api.WGAPIException;
import de.innovationgate.webgate.api.WGColumnSet;
import de.innovationgate.webgate.api.WGDeletedException;
import de.innovationgate.webgate.api.WGFileAnnotations;
import de.innovationgate.webgate.api.WGArea;
import de.innovationgate.webgate.api.WGAuthorisationException;
import de.innovationgate.webgate.api.WGBackendException;
import de.innovationgate.webgate.api.WGContentKey;
import de.innovationgate.webgate.api.WGContentStoreVersionException;
import de.innovationgate.webgate.api.WGContentType;
import de.innovationgate.webgate.api.WGCreationException;
import de.innovationgate.webgate.api.WGDatabase;
import de.innovationgate.webgate.api.WGDatabaseCore;
import de.innovationgate.webgate.api.WGDocument;
import de.innovationgate.webgate.api.WGDocumentCore;
import de.innovationgate.webgate.api.WGFactory;
import de.innovationgate.webgate.api.WGFileDerivateMetaData;
import de.innovationgate.webgate.api.WGPageOrderSet;
import de.innovationgate.webgate.api.WGWrongRevisionException;
import de.innovationgate.webgate.api.WGInvalidDatabaseException;
import de.innovationgate.webgate.api.WGLanguage;
import de.innovationgate.webgate.api.WGNotSupportedException;
import de.innovationgate.webgate.api.WGProcedureException;
import de.innovationgate.webgate.api.WGRelationData;
import de.innovationgate.webgate.api.WGResultSetCore;
import de.innovationgate.webgate.api.WGSessionContext;
import de.innovationgate.webgate.api.WGStructEntry;
import de.innovationgate.webgate.api.WGUserAccess;
import de.innovationgate.webgate.api.WGUserDetails;
import de.innovationgate.webgate.api.auth.AuthenticationModule;
import de.innovationgate.webgate.api.auth.AuthenticationSession;
import de.innovationgate.webgate.api.auth.MasterLoginAuthSession;
import de.innovationgate.webgate.api.fake.WGFakeDocument;
import de.innovationgate.webgate.api.fake.WGFakeLanguage;
import de.innovationgate.webgate.api.utils.MasterSessionTask;
import de.innovationgate.webgate.api.workflow.WGDefaultWorkflowEngine;

/**
 * Template class to implement a simple content source with a subset of WGAPI features.
 * 
 * The Simple Content Source manages either JavaBeans or java.util.Map objects as content. 
 * While JavaBeans are queried for their properties as Items, Maps serve their mapped values as Items. 
 * 
 * Data objects are stored in so-called "folders". The SCS defines itself what folders are available.
 * The WGAPI will abstract this folders to Areas.
 * 
 * Data objects additionally need a data key, under which they will get stored inside the folder. 
 * Each key needs to be unique inside the folder. The keys can be generated by the SCS implementation or
 * entered from outside while creating contents.
 * 
 * The method {@link de.innovationgate.webgate.api.templates#SimpleContentSource.init init} that needs to be implemented should return an object of type {@link de.innovationgate.webgate.api.templates#ContentSourceSpecs ContentSourceSpecs}.
 * This object contains flags in which the implementation will tell the template what features to activate. 
 * 
 * Methods returning multiple contents should return them as objects of type java.util.Map.
 * The returned Map keys should match the data keys of the available data objects.
 * The data objects can be put as Map values if they are immediately available.
 * However the Map values can also be left empty, if they are not. 
 * In that case the data objects will be retrieved lazily by calling method {@link de.innovationgate.webgate.api.templates#SimpleContentSource.getContent getContent}.
 * 
 */
public abstract class SimpleContentSource implements WGDatabaseCore {
	
	public static final String DUMMY_AREA = "home";
	public static final String COPTION_LANGUAGE = "Language";

	private WGDatabase _db;
	private String _path;
	private ContentSourceSpecs _specs;
	private Map _procs = new HashMap();
	
	private ThreadLocal userName = new ThreadLocal();
	private String _fakeLanguage = null;

	/* (Kein Javadoc)
	 * @see de.innovationgate.webgate.api.WGDatabaseCore#open(de.innovationgate.webgate.api.WGDatabase, java.lang.String, java.lang.String, java.lang.String)
	 */
	public WGUserAccess open(WGDatabase db, String path, String user, String pwd, boolean prepareOnly) throws WGAPIException {

		_db = db;
		_path = path;

		// Initialize
		_specs = init(db, path);
		if (_specs == null) {
			return new WGUserAccess(WGDatabase.ANONYMOUS_USER, WGDatabase.ACCESSLEVEL_NOTLOGGEDIN);
		}
		
		// Init fake language
		_fakeLanguage = (String) db.getCreationOptions().get(COPTION_LANGUAGE);
		if (_fakeLanguage == null) {
			_fakeLanguage = Locale.getDefault().getLanguage();
		}

		// Find procedures
		Method[] methods = getClass().getMethods();
		Method method;
		for (int i = 0; i < methods.length; i++) {
			method = methods[i];
			
			if ((method.getModifiers() & Modifier.PUBLIC) == Modifier.PUBLIC) { // Only public methods!
				
				// Either the method is defined in procedures list (when available) or it must be declared in the implementation class
				if (_specs.getProcedures() != null && _specs.getProcedures().contains(method.getName())) { 
					_procs.put(method.getName(), method);
				}
				else if (method.getDeclaringClass().equals(getClass())) {
					_procs.put(method.getName(), method);
				}
			}
		}

		// Open session
		return openSession(MasterLoginAuthSession.getInstance(), pwd, true);
		
	}

	/* (Kein Javadoc)
	 * @see de.innovationgate.webgate.api.WGDatabaseCore#openSession(java.lang.String, java.lang.String)
	 */
	public WGUserAccess openSession(AuthenticationSession authSession, Object pwd, boolean master) throws WGAPIException {
		
		if (master && !_specs.isUseMasterLogin()) {
			return new WGUserAccess(authSession.getDistinguishedName(), WGDatabase.ACCESSLEVEL_READER);
		}
		
        String pwdStr = pwd != null ? pwd.toString() : null;
        
		int accLevel;
		try {
			accLevel = login(authSession.getDistinguishedName(), pwdStr);
		}
		catch (WGAuthorisationException e) {
			return new WGUserAccess(authSession.getDistinguishedName(), WGDatabase.ACCESSLEVEL_NOTLOGGEDIN);
		}
		catch (WGAPIException e) {
			WGFactory.getLogger().error("Error opening scs session", e);
			return new WGUserAccess(authSession.getDistinguishedName(), WGDatabase.ACCESSLEVEL_NOTLOGGEDIN);
		}
		if (accLevel > WGDatabase.ACCESSLEVEL_NOACCESS) {
			userName.set(authSession.getDistinguishedName());
		}
		return new WGUserAccess(authSession.getDistinguishedName(), accLevel);
	}
	
	/**
	 * Implement to specify login behaviour for users opening single sessions.
	 * @param user login username
	 * @param pwd login password
	 * @return A constant WGDatabase.ACCESSLEVEL_... to determine what access the user has on the database, WGDatabase.ACCESSLEVEL_NOTLOGGEDIN if the login should fail
	 * @throws WGAPIException
	 */
	public abstract int login(String user, String pwd) throws WGAPIException;
	/**
	 * Called when a user session is closed to cleanup resources of it
	 * @throws WGAPIException 
	 */
	public abstract void logout() throws WGAPIException;

	/* (Kein Javadoc)
	 * @see de.innovationgate.webgate.api.WGDatabaseCore#close()
	 */
	public void close() throws WGAPIException {
		
		destroy();
		
	}
	
	/* (Kein Javadoc)
	 * @see de.innovationgate.webgate.api.WGDatabaseCore#closeSession()
	 */
	public void closeSession() throws WGAPIException {
		logout();
		userName.remove();
	}

	/* (Kein Javadoc)
	 * @see de.innovationgate.webgate.api.WGDatabaseCore#getTitle()
	 */
	public abstract String getTitle() throws WGBackendException;


	/* (Kein Javadoc)
	 * @see de.innovationgate.webgate.api.WGDatabaseCore#getTypeName()
	 */
	public abstract String getTypeName();
		

	/* (Kein Javadoc)
	 * @see de.innovationgate.webgate.api.WGDatabaseCore#getLastChanged()
	 */
	public Date getRevision() throws WGAPIException {
		return getLastModified();
	}

	/* (Kein Javadoc)
	 * @see de.innovationgate.webgate.api.WGDatabaseCore#getMetaData(java.lang.String)
	 */
	public Object getExtensionData(String name) throws WGAPIException {
	    throw new WGContentStoreVersionException("Database metadata", WGDatabase.CSVERSION_WGA5);
	}
	
	/* (Kein Javadoc)
     * @see de.innovationgate.webgate.api.WGDatabaseCore#getMetaData(java.lang.String)
     */
    public void writeExtensionData(String name, Object value) throws WGAPIException {
        throw new WGContentStoreVersionException("Database metadata", WGDatabase.CSVERSION_WGA5);
    }

	/* (Kein Javadoc)
	 * @see de.innovationgate.webgate.api.WGDatabaseCore#getRoles()
	 */
	public List getRoles() {
		return Arrays.asList(new String[] {WGDatabase.ROLE_CONTENT});
	}

	/* (Kein Javadoc)
	 * @see de.innovationgate.webgate.api.WGDatabaseCore#hasFeature(java.lang.String)
	 */
	public boolean hasFeature(String feature) {

		if (feature.equals(WGDatabase.FEATURE_COMPLEXVALUES)) {
			return true;
		}
		else if (feature.equals(WGDatabase.FEATURE_EDITABLE)) {
			return _specs.isWritable();
		}
		else if (feature.equals(WGDatabase.FEATURE_FULLCONTENTFEATURES)) {
			return false;
		}
		else if (feature.equals(WGDatabase.FEATURE_GENERATES_STRUCTKEYS)) {
			return _specs.isCalculatesKeys();
		}
		else if (feature.equals(WGDatabase.FEATURE_ACCEPTS_STRUCTKEYS)) {
			return true;
		}
		else if (feature.equals(WGDatabase.FEATURE_HIERARCHICAL)) {
			return _specs.isBrowseable();
		}
		else if (feature.equals(WGDatabase.FEATURE_LASTCHANGED)) {
			return _specs.isMaintainsLastChanged();
		}
		else if (feature.equals(WGDatabase.FEATURE_NATIVEEXPRESSIONS)) {
			return false;
		}
		else if (feature.equals(WGDatabase.FEATURE_QUERYABLE)) {
			return _specs.isQueryable();
		}
		else if (feature.equals(WGDatabase.FEATURE_STORESVARS)) {
			return false;
		}
		else if (feature.equals(WGDatabase.FEATURE_USE_OBJECTS_AS_REFERENCES)) {
			return false;
		}
		else if (feature.equals(WGDatabase.FEATURE_PERFORMS_BACKEND_LOGIN)) {
		    return true;
		}
		else if (feature.equals(WGDatabase.FEATURE_CONTENT_READ_PROTECTION)) {
		    return _specs.isContentReadProtected();
		}
		else {
			return false;
		}


	}

	@Override
	public Iterator<WGDocumentCore> getChildEntries(WGStructEntry structEntry, WGPageOrderSet order) throws WGAPIException {
		return Collections.EMPTY_LIST.iterator();
	}

	/* (Kein Javadoc)
	 * @see de.innovationgate.webgate.api.WGDatabaseCore#getRootEntries(de.innovationgate.webgate.api.WGArea)
	 */
	public Iterator<WGDocumentCore> getRootEntries(WGArea area, WGPageOrderSet pageOrder) throws WGAPIException {

		BeanFolder folder = (BeanFolder) area.getCore();
		Map contentCores = browse(folder.getName());
		Iterator contentCoreKeys = contentCores.keySet().iterator();
		List<WGDocumentCore> contents = new ArrayList<WGDocumentCore>();
		Object key;
		Object bean;
		while (contentCoreKeys.hasNext()) {
			key = contentCoreKeys.next();
			bean = contentCores.get(key);
			contents.add(new WrappedKey(this, new BeanKey(folder.getName(), key), bean, true));
		}
		
		return contents.iterator();
		

	}

	/* (Kein Javadoc)
	 * @see de.innovationgate.webgate.api.WGDatabaseCore#getStructEntryByKey(java.lang.Object)
	 */
	public WGDocumentCore getStructEntryByKey(Object key) throws WGAPIException {
		
		if (key == null) {
			return null;
		}
		
		return new WrappedKey(this, (BeanKey) key, null, true);
	}

	/* (Kein Javadoc)
	 * @see de.innovationgate.webgate.api.WGDatabaseCore#getParentEntry(de.innovationgate.webgate.api.WGStructEntry)
	 */
	public WGDocumentCore getParentEntry(WGStructEntry entry) throws WGAPIException {
		return null;
	}

	/* (Kein Javadoc)
	 * @see de.innovationgate.webgate.api.WGDatabaseCore#getAllContent(de.innovationgate.webgate.api.WGStructEntry)
	 */
	public List getAllContent(WGStructEntry structEntry, boolean includeArchived) throws WGAPIException {
		
		WrappedKey key = (WrappedKey) structEntry.getCore();
		Object bean = key.getBean();
		List list = new ArrayList();
		list.add(createWrapper(key.getKey(), bean, true));
		return list;
	}

	/* (Kein Javadoc)
	 * @see de.innovationgate.webgate.api.WGDatabaseCore#getContentByKey(de.innovationgate.webgate.api.WGContentKey)
	 */
	public WGDocumentCore getContentByKey(WGContentKey key) throws WGAPIException {
		
		return fastAccess(WGDocument.TYPE_CONTENT, key.getStructKey());
	}

	/* (Kein Javadoc)
	 * @see de.innovationgate.webgate.api.WGDatabaseCore#getContentByName(java.lang.String, java.lang.String)
	 */
	public WGDocumentCore getContentByName(String strName, String strLanguage) throws WGAPIException {
		return null;
	}

	/* (Kein Javadoc)
	 * @see de.innovationgate.webgate.api.WGDatabaseCore#getDummyContent(java.lang.String)
	 */
	public WGDocumentCore getDummyContent(String language) throws WGAPIException {
		return new WGFakeDocument(_db, WGDocument.TYPE_CONTENT);
	}

	/* (Kein Javadoc)
	 * @see de.innovationgate.webgate.api.WGDatabaseCore#query(java.lang.String, java.lang.String, java.util.Map)
	 */
	public WGResultSetCore query(String type, String query, Map parameters) throws WGAPIException {
		
		Map results = find(type, query, parameters);
		String folder = extractFolder(type, query, parameters);
		if (folder == null) {
			folder = type;
		}
		
		return new LazyBeanList(this, folder, results);		
		
	}

	/**
	 * Helps to retrieve the folder for query results based on the given query parameters. Base implementation returns null = the folder is the type of the query. Should be overwritten if this is different.
	 * @param type The query type used for the query
	 * @param query The query itself
	 * @param parameters The parameters given to the query
	 * @return The folder name if it is retrievable, null otherwise.
	 */
	public String extractFolder(String type, String query, Map parameters) throws WGAPIException {
		return null;
	}

	/* (Kein Javadoc)
	 * @see de.innovationgate.webgate.api.WGDatabaseCore#getDesignObjects(int)
	 */
	public List getDesignObjects(int type) throws WGAPIException {
	
		if (type == WGDocument.TYPE_AREA) {
			String[] folderNames = getFolders();
			List folders = new ArrayList();
			if (folderNames != null) {
    			for (int i=0; i < folderNames.length; i++) {
    				folders.add(new BeanFolder(this, folderNames[i]));
    			}
			}
			return folders;
		}
		else if (type == WGDocument.TYPE_LANGUAGE) {
		    return Collections.singletonList(new WGFakeLanguage(_db, _fakeLanguage, _fakeLanguage));
		}
			
		return null;
		
	}

	/* (Kein Javadoc)
	 * @see de.innovationgate.webgate.api.WGDatabaseCore#getDesignObject(int, java.lang.String, java.lang.String)
	 */
	public WGDocumentCore getDesignObject(int type, String name, String strMediaKey) throws WGAPIException {
		
		if (type == WGDocument.TYPE_AREA) {
			return new BeanFolder(this, name);
		}
		else if (type == WGDocument.TYPE_LANGUAGE && _fakeLanguage.equals(name)) {
			return new WGFakeLanguage(_db, name, name);
		}
			
		return null;
		
		
	}

	/* (Kein Javadoc)
	 * @see de.innovationgate.webgate.api.WGDatabaseCore#getUserProfile(java.lang.String)
	 */
	public WGDocumentCore getUserProfile(String name) throws WGAPIException {
		return null;
	}

	/* (Kein Javadoc)
	 * @see de.innovationgate.webgate.api.WGDatabaseCore#createUserProfile(java.lang.String, int)
	 */
	public WGDocumentCore createUserProfile(String name, int type) throws WGAPIException {
		return null;
	}

	/* (Kein Javadoc)
	 * @see de.innovationgate.webgate.api.WGDatabaseCore#fastAccess(java.lang.Object)
	 */
	public WGDocumentCore fastAccess(int type, Object key) throws WGAPIException {
		BeanKey beanKey = (BeanKey) key;
		if (type == WGDocument.TYPE_CONTENT) {
		    
		    // The corresponding struct entry already has it's core we maybe can retrieve the bean from it
		    WGStructEntry entry = _db.getStructEntryByKey(beanKey);
		    if (entry != null) {
    		    WrappedKey entryCore = (WrappedKey) entry.getCore();
    		    Object bean = entryCore.getBean();
    		    if (bean == null) {
    		        throw new WGDeletedException(WGDocument.buildDocumentKey(entryCore, _db).toString());
    		    }
    		    
                return createWrapper(beanKey, bean, true);
            
		    
			//Object bean = getContent(beanKey.getFolder(), beanKey.getKey());
            //if (bean != null) {
            //    return createWrapper(beanKey, bean, true);
            //}
            }
            else {
                WGFactory.getLogger().info("Could not re-retrieve bean '" + beanKey.getKey() + "' in folder '" + beanKey.getFolder() + "'");
                return null;
            }
		}
		else {
			return null;
		}
		
	}

	/* (Kein Javadoc)
	 * @see de.innovationgate.webgate.api.WGDatabaseCore#parseStructKey(java.lang.String)
	 */
	public Object parseStructKey(String key) throws WGAPIException {
		
		key = BeanKey.decodeKey(key);
		int commaPos = key.indexOf(",");
		if (commaPos == -1) {
			return null;
		}
		String folderPart = key.substring(0, commaPos);
		String keyPart = key.substring(commaPos + 1);
		Object realKey = convertToKey(keyPart, folderPart);
				
		return new BeanKey(folderPart, realKey);
	}
	
	/**
	 * Converts the string representation of a data key to it's real data type.
	 * @param key The string representatino of the data key
	 * @param folder The folder of the data key
	 * @return The converted data key in it's original data type.
	 * @throws WGAPIException 
	 */
	public abstract Object convertToKey(String key, String folder) throws WGAPIException;

	/* (Kein Javadoc)
	 * @see de.innovationgate.webgate.api.WGDatabaseCore#cleanup()
	 */
	public void cleanup() {
	}

	/* (Kein Javadoc)
	 * @see de.innovationgate.webgate.api.WGDatabaseCore#createDesignDocument(int, java.lang.String, java.lang.String)
	 */
	public WGDocumentCore createDesignDocument(int type, String name, String mediaKey) throws WGAPIException {
		return null;
	}

	/* (Kein Javadoc)
	 * @see de.innovationgate.webgate.api.WGDatabaseCore#createStructEntry(de.innovationgate.webgate.api.WGDocument, de.innovationgate.webgate.api.WGContentType)
	 */
	public WGDocumentCore createStructEntry(Object key, WGDocument reference, WGContentType contentType) throws WGAPIException {
		
		if (reference instanceof WGStructEntry) {
			throw new WGCreationException("Creation of child content not possible in this implementation");
		}
		
		BeanFolder folder = (BeanFolder) reference.getCore();
		return new WrappedKey(this, new BeanKey(folder.getName(), key), null, false);
				
	}

	/* (Kein Javadoc)
	 * @see de.innovationgate.webgate.api.WGDatabaseCore#createContent(de.innovationgate.webgate.api.WGStructEntry, de.innovationgate.webgate.api.WGLanguage, java.lang.String, int)
	 */
	public WGDocumentCore createContent(WGStructEntry structEntry, WGLanguage language, String title, int version) throws WGAPIException {
		
		BeanKey beanKey = ((WrappedKey) structEntry.getCore()).getKey();
		
		Object bean = createContent(beanKey.getFolder());
		return createWrapper(beanKey, bean, false);
	}

	/* (Kein Javadoc)
	 * @see de.innovationgate.webgate.api.WGDatabaseCore#getDefaultWorkflowEngine()
	 */
	public Class getDedicatedWorkflowEngine() {
		return WGDefaultWorkflowEngine.class;
	}

	/* (Kein Javadoc)
	 * @see de.innovationgate.webgate.api.WGDatabaseCore#isMemberOfUserList(java.util.List)
	 */
	public boolean isMemberOfUserList(List userList) throws WGAPIException {
		return false;
	}

	/* (Kein Javadoc)
	 * @see de.innovationgate.webgate.api.WGDatabaseCore#createCopy(de.innovationgate.webgate.api.WGDocumentCore)
	 */
	public WGDocumentCore createCopy(WGDocumentCore original) throws WGAPIException {
		return null;
	}

	/* (Kein Javadoc)
	 * @see de.innovationgate.webgate.api.WGDatabaseCore#beginTransaction()
	 */
	public boolean beginTransaction() {
		return false;
	}

	/* (Kein Javadoc)
	 * @see de.innovationgate.webgate.api.WGDatabaseCore#rollbackTransaction()
	 */
	public boolean rollbackTransaction() {
		return false;
	}

	/* (Kein Javadoc)
	 * @see de.innovationgate.webgate.api.WGDatabaseCore#commitTransaction()
	 */
	public boolean commitTransaction() {
		return false;
	}
	
	/**
	 * Implement to retrieve a single data object by key information. 
	 * This gets called by lazy data fetching, when result maps contain only keys but no data objects. 
	 * @param folder The folder of the object
	 * @param key The data key of the object
	 * @return The data object if there is any under this key, null otherwise
	 * @throws WGAPIException 
	 */
	public abstract Object getContent(String folder, Object key) throws WGAPIException;
	/**
	 * Implement functionality to insert a new data object to a folder
	 * @param folder The folder to insert this data object
	 * @param key The key for this data object. You may ignore this if this SCS implementation generates it's own keys.
	 * @param bean The data object to store
	 * @return true, if insertion succeeded, false otherwise
	 * @throws WGAPIException 
	 */
	public abstract boolean insertContent(String folder, Object key, Object bean) throws WGAPIException;
	/**
	 * Implement behaviour to update the database backend with new data for a data object
	 * @param folder The folder of the data object
	 * @param key The data key of the data object
	 * @param bean The data object itself.
	 * @return true, if update succeeded, false otherwise
	 * @throws WGAPIException 
	 */
	public abstract boolean updateContent(String folder, Object key, Object bean) throws WGAPIException;
	/**
     * Creates a new content object in the given folder
	 * @param folder The folder name to create the content 
	 * @return The new content object
	 */
	public abstract Object createContent(String folder) throws WGAPIException;
	/**
	 * Used to implement querying behaviour to the SCS implementation.
	 * @param type The query type (which by default should be the folder at which the query is directed)
	 * @param query The query
	 * @param parameters Query parameters. Map keys are Constants WGDatabase.QUERYOPTION_...
	 * @return A map containing the results. Map keys should be data keys. Map values should be data objects or null to use lazy retrieval
	 * @throws WGAPIException
	 */
	public abstract Map find(String type, String query, Map parameters) throws WGAPIException;
	/**
	 * Implement to return a last modified date. May return null if this feature is not supported by this SCS implementation.
	 * @throws WGAPIException
	 */
	public abstract Date getLastModified() throws WGAPIException;
	/**
	 * Implement behaviour to remove a data object from a folder
	 * @param folder Folder of the data object
	 * @param key Key of the data object
	 * @throws WGAPIException 
	 */
	public abstract void removeContent(String folder, Object key) throws WGAPIException;
	/**
	 * Implement to initialize the SCS implementation when the database gets opened.
	 * @param db The WGAPI database object, that will represent this SCS implementation
	 * @param path The database path
	 * @return Specs of this implementation, telling the template what features will get used
	 * @throws WGAPIException
	 */
	public abstract ContentSourceSpecs init(WGDatabase db, String path) throws WGAPIException;
	/**
	 * Callback method, that gets called when the SCS is closed completely.
	 */
	public abstract void destroy();


	
	/**
	 * Implement to return the available folders
	 * @return String array of folder names
	 * @throws WGAPIException
	 */
	public abstract String[] getFolders() throws WGAPIException;
	
	/**
	 * Browses the given folder for contents.
	 * @param folder The folder name
	 * @return A map of data objects. Use data keys as Map keys. Use data objects as Map values or leave values null to use lazy fetching behaviour.
	 * @throws WGAPIException 
	 */
	public abstract Map browse(String folder) throws WGAPIException;

	protected String getFakeLanguage() {
		return _fakeLanguage;
	}

	/**
	 * Returns the specs that the init-Method returned for this SCS implementation.
	 */
	public ContentSourceSpecs getSpecs() {
		return _specs;
	}
	
	protected Logger getLog() {
		return WGFactory.getLogger();
	}


	/* (Kein Javadoc)
	 * @see de.innovationgate.webgate.api.WGDatabaseCore#execProcedure(java.lang.String, java.util.List)
	 */
	public Object execProcedure(String procName, List args) throws WGProcedureException, WGBackendException {

		Method proc = (Method) _procs.get(procName);
		if (proc == null) {
			throw new WGProcedureException("No procedure of name '" + procName + "'");
		}
		
		Object result = null;
		try {
			result = proc.invoke(this, args.toArray());
		}
		catch (IllegalArgumentException e) {
			throw new WGProcedureException("Argument types and/or count does not match arguments of procedure '" + procName + "'");
		}
		catch (IllegalAccessException e) {
			throw new WGProcedureException("Unable to access procedure '" + procName + "'. Method not visible.");
		}
		catch (InvocationTargetException e) {
			WGFactory.getLogger().error("Error invoking procedure '" + procName + "':" + e.getTargetException().getMessage(), e);
			throw new WGProcedureException("Error invoking procedure '" + procName + "':" + e.getTargetException().getMessage());
		}	
		
		return result;	
		

	}





	/* (Kein Javadoc)
	 * @see de.innovationgate.webgate.api.WGDatabaseCore#resultIsTrue(java.lang.Object, de.innovationgate.webgate.api.WGDocument)
	 */
	public boolean resultIsTrue(Object result, WGDocument doc) {
		return false;
	}	/* (Kein Javadoc)
	 * @see de.innovationgate.webgate.api.WGDatabaseCore#resultIsFalse(java.lang.Object, de.innovationgate.webgate.api.WGDocument)
	 */
	public boolean resultIsFalse(Object result, WGDocument doc) {
		return false;
	}	/* (Kein Javadoc)
	 * @see de.innovationgate.webgate.api.WGDatabaseCore#getACL()
	 */
	public WGACLCore getACL() {
		return null;
	}

	/* (Kein Javadoc)
	 * @see de.innovationgate.webgate.api.WGDatabaseCore#getNativeObject()
	 */
	public Object getNativeObject() throws WGBackendException {
		return null;
	}

	protected WGDatabase getDb() {
		return _db;
	}
	
	/**
     * Creates a wrapper for the given content object that implements WGDocumentCore for it
	 * @param key The key of the content object
	 * @param bean The content object
	 * @param saved Boolean showing if the object already has been saved
	 * @return The content wrapper, ready to be used  as WGDocumentCore
	 * @throws WGAPIException
	 */
	public BeanWrapper createWrapper(BeanKey key, Object bean, boolean saved) throws WGAPIException {
		
		if (bean instanceof Map) {
			return new MapWrapper(this, key, (Map) bean, saved, (key.getKey() instanceof TemporaryKey));
		}
		else {
			return new BeanWrapper(this, key, bean, saved, (key.getKey() instanceof TemporaryKey));
		}
	}

	/**
	 * Method to calculate the key to a newly created data object. This is necessary to overwrite if the SCS implementation supports calculating keys. 
	 * @param folder the folder where the data object gets stored
	 * @param _bean The data object
	 * @return The calculated key for the data object
	 * @throws WGAPIException
	 */
	public Object calculateKey(String folder, Object _bean) throws WGAPIException {
		return null;
	}

	/* (Kein Javadoc)
	 * @see de.innovationgate.webgate.api.WGDatabaseCore#refresh()
	 */
	public void refresh() {}
	
	@Override
	public void clearSessionCache() throws WGAPIException {
	}

	/* (Kein Javadoc)
	 * @see de.innovationgate.webgate.api.WGDatabaseCore#getUpdatedDocumentsSince(java.util.Date)
	 */
	public List getUpdatedDocumentsSince(Date cutoff) throws WGAPIException {
		return null;
	}

    /* (non-Javadoc)
     * @see de.innovationgate.webgate.api.WGDatabaseCore#moveStructEntry(de.innovationgate.webgate.api.WGStructEntry, de.innovationgate.webgate.api.WGDocument)
     */
    public boolean moveStructEntry(WGStructEntry entry, WGDocument newParent) throws WGAPIException {
        return false;
    }
    /* (non-Javadoc)
     * @see de.innovationgate.webgate.api.WGDatabaseCore#hasContents(de.innovationgate.webgate.api.WGStructEntry)
     */
    public int getContentCount(WGStructEntry entry) throws WGBackendException {
        throw new WGNotSupportedException("Not supported");
    }
    /* (non-Javadoc)
     * @see de.innovationgate.webgate.api.WGDatabaseCore#setCurrentSession(de.innovationgate.webgate.api.WGSessionContext)
     */
    public void setCurrentSession(WGSessionContext context) {
    }

    /* (non-Javadoc)
     * @see de.innovationgate.webgate.api.WGDatabaseCore#convertFileNameForAttaching(java.lang.String)
     */
    public String convertFileNameForAttaching(String name) {
        return null;
    }

    /* (non-Javadoc)
     * @see de.innovationgate.webgate.api.WGDatabaseCore#getAllowedCredentialClasses()
     */
    public Class[] getAllowedCredentialClasses() {
        return new Class[] { String.class };
    }
    
    public List queryUserProfileNames(String type, String query, Map params) throws WGAPIException {
        return null;
    }
    
    /*
     *  (non-Javadoc)
     * @see de.innovationgate.webgate.api.WGDatabaseCore#getDeletions(java.util.Set)
     */
    public Set getDeletions(Set contentKeys) throws WGAPIException {     
        return Collections.EMPTY_SET;
    }

    public List getAllContentKeys(boolean includeArchived) throws WGAPIException {
        throw new WGNotSupportedException("This method is not supported by this WGAPI implementation");
    }
    
    public void beginUpdate() {
    	
    }

    public WGDocumentCore getStructEntryByName(String strName) throws WGAPIException {
        throw new WGNotSupportedException("This method is not supported by this WGAPI implementation");
    }

    public Date getRevisionDate(Comparable lastChanged) throws WGAPIException, WGWrongRevisionException {
        try {
            return (Date) lastChanged;
        }
        catch (ClassCastException e) {
            throw new WGWrongRevisionException(Date.class); 
        }
    }

    public List getUpdateLogs(Comparable cutoff) throws WGAPIException {
        return null;
    }

    public double getContentStoreVersion() throws WGAPIException {
        return WGDatabase.CSVERSION_NO_CONTENTSTORE;
    }
    
    @Override
    public int getContentStorePatchLevel() throws WGAPIException {
        return 0;
    }

    public void removeExtensionData(String name) throws WGAPIException {
    }
    
    public List<String> getExtensionDataNames() throws WGAPIException {
        return Collections.emptyList();
    }

    public List<WGRelationData> getIncomingRelations(Object structKey, String language, String contentClass, String relName, String relGroupName, Boolean includeUnreleased, WGColumnSet order) throws WGAPIException {
        return Collections.emptyList();
    }

    public boolean isContentTypeUsed(WGContentType ct) throws WGAPIException {
        return true;
    }

    public boolean isLanguageUsed(WGLanguage lang) throws WGAPIException {
        return true;
    }
    
    
    public boolean isBackendServiceSupported(String serviceName) {
        return false;
    }
    
    public Object callBackendService(String serviceName, Object[] params) throws WGAPIException {
        throw new WGNotSupportedException("No backend services");
    }

}
